#define _POSIX_SOURCE

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <string.h>
#include <termios.h>
#include <time.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>

#include "debug.h"
#include "serial.h"

#define SERIAL_DBG_TAG  "serial"

#define MAX_SERIAL_NUMS 8
#define MAX_PATH_SIZE   32

struct serial_dev
{
    int fd;
    char devpath[MAX_PATH_SIZE];
};

static struct serial_dev serial[MAX_SERIAL_NUMS] =
{
#if (MAX_SERIAL_NUMS > 0)
    [0] = {
        .fd = -1,
        .devpath = {0},
    },
#endif
#if (MAX_SERIAL_NUMS > 1)
    [1] = {
        .fd = -1,
        .devpath = {0},
    },
#endif
#if (MAX_SERIAL_NUMS > 2)
    [2] = {
        .fd = -1,
        .devpath = {0},
    },
#endif
#if (MAZ_SERIAL_NUMS > 3)
    [3] = {
        .fd = -1,
        .devpath = {0},
    },
#endif
#if (MAZ_SERIAL_NUMS > 4)
    [4] = {
        .fd = -1,
        .devpath = {0},
    },
#endif
#if (MAZ_SERIAL_NUMS > 5)
    [5] = {
        .fd = -1,
        .devpath = {0},
    },
#endif
#if (MAZ_SERIAL_NUMS > 6)
    [6] = {
        .fd = -1,
        .devpath = {0},
    },
#endif
#if (MAZ_SERIAL_NUMS > 7)
    [7] = {
        .fd = -1,
        .devpath = {0},
    },
#endif
};

int serial_init(const char *devpath, speed_t speed, int csize, char parity, int stopbits, unsigned int timeout)
{
    int i, j;
    int ret;
    tcflag_t par;
    tcflag_t stopb;
    struct termios options;

    for (i = 0; i < MAX_SERIAL_NUMS; i++)
    {
        if ((serial[i].fd != -1) && (strncmp(devpath, serial[i].devpath, MAX_PATH_SIZE)) == 0)
        {
            debug(WARNING, SERIAL_DBG_TAG, "%s", "the serial device has already been opened\n");
            return -ESERIALOPENERR;
        }
    }

    for (i = 0; i < MAX_SERIAL_NUMS; i++)
    {
        if (serial[i].fd == -1)
            break;
    }

    if (i == MAX_SERIAL_NUMS)
    {
        debug(ERROR, SERIAL_DBG_TAG, "%s", "opend too much devices\n");
        return -ESERIALOPENERR;
    }

    /* open the serial device */
    serial[i].fd = open(devpath, O_RDWR | O_NOCTTY);

    if (serial[i].fd == -1)
    {
        perror(SERIAL_DBG_TAG);
        return -ESERIALOPENERR;
    }

    strncpy(serial[i].devpath, devpath, MAX_PATH_SIZE);
    /* get the termios */
    ret = tcgetattr(serial[i].fd, &options);

    if (ret == -1)
    {
        perror(SERIAL_DBG_TAG);
        close(serial[i].fd);
        serial[i].fd = -1;
        memset(serial[i].devpath, 0, sizeof(serial[i].devpath));
        return -ESERIALOPENERR;
    }

    debug(DEBUG, SERIAL_DBG_TAG, "termios.c_iflag = 0x%08x\n", options.c_iflag);
    debug(DEBUG, SERIAL_DBG_TAG, "termios.c_oflag = 0x%08x\n", options.c_oflag);
    debug(DEBUG, SERIAL_DBG_TAG, "termios.c_cflag = 0x%08x\n", options.c_cflag);
    debug(DEBUG, SERIAL_DBG_TAG, "termios.c_lflag = 0x%08x\n", options.c_lflag);

    for (j = 0; j < NCCS; j++)
        debug(DEBUG, SERIAL_DBG_TAG, "termios.c_cc[%d] = 0x%08x\n", j, options.c_cc[j]);

    debug(DEBUG, "", "%s", "\n");
    /* set baud rate */
    ret = cfsetispeed(&options, speed);

    if (ret == -1)
    {
        perror(SERIAL_DBG_TAG);
        close(serial[i].fd);
        serial[i].fd = -1;
        memset(serial[i].devpath, 0, sizeof(serial[i].devpath));
        return -ESERIALBAUDERR;
    }

    ret = cfsetospeed(&options, speed);

    if (ret == -1)
    {
        perror(SERIAL_DBG_TAG);
        close(serial[i].fd);
        serial[i].fd = -1;
        memset(serial[i].devpath, 0, sizeof(serial[i].devpath));
        return -ESERIALBAUDERR;
    }

    /* set c_iflag */
    if (parity == 'o' || parity == 'O' || parity == 'e' || parity == 'E')
        par = INPCK;    /* Enable input parity checking */
    else
        par = IGNPAR;   /* Ignore framing errors and parity errors */

    options.c_iflag &= ~(BRKINT | IGNPAR | PARMRK | INPCK | ISTRIP | INLCR | IGNCR | \
                         ICRNL | IUCLC | IXON | IXANY | IXOFF | IMAXBEL | IUTF8);
    options.c_iflag |= (IGNBRK | par);  /* Ignore BREAK condition on input */
    /* set c_oflag */
    options.c_oflag &= ~OPOST;  /* Do not care about output processing */

    /* set c_cflag */
    if (csize != CS5 && csize != CS6 && csize != CS7 && csize != CS8)
    {
        debug(ERROR, SERIAL_DBG_TAG, "%s", "character size is error\n");
        close(serial[i].fd);
        serial[i].fd = -1;
        memset(serial[i].devpath, 0, sizeof(serial[i].devpath));
        return -ESERIALCSERR;
    }

    if (stopbits != 1 && stopbits != 2)
    {
        debug(ERROR, SERIAL_DBG_TAG, "%s", "stopbit(s) is error\n");
        close(serial[i].fd);
        serial[i].fd = -1;
        memset(serial[i].devpath, 0, sizeof(serial[i].devpath));
        return -ESERIALSTBERR;
    }
    else if (stopbits == 2)
        stopb = CSTOPB;
    else
        stopb = 0;

    if (parity == 'o' || parity == 'O' || parity == 'e' || parity == 'E')
    {
        if (parity == 'o' || parity == 'O')
            par = PARENB | PARODD;
        else
            par = PARENB;
    }
    else
        par = 0;

    options.c_cflag &= ~(CSIZE | CSTOPB | PARENB | PARODD | HUPCL );//| CRTSCTS);
    options.c_cflag |= (csize | stopb | CREAD | par | CLOCAL);
    /* set c_lfalg */
    options.c_lflag &= ~(ISIG | ICANON | ECHO | ECHONL | NOFLSH | TOSTOP | IEXTEN);
    options.c_lflag |= NOFLSH;
    /* set c_cc */
    //memset(&options.c_cc, 0, sizeof(options.c_cc));
    /* MIN == 0; TIME > 0: TIME specifies the limit for a timer in tenths of a second.
     * The timer is  started  when  read is called. read returns  either  when at least
     * one byte of data is available, or when the timer expires.  If the timer expires
     * without any input becoming available, read(2) returns 0.
     * MIN > 0; TIME == 0: read blocks until the lesser of MIN bytes or the number of bytes
     * requested are available, and returns the lesser of these two values.
     * MIN > 0; TIME > 0: TIME specifies the limit for a timer in tenths of a second.  Once an
     * initial byte  of  input  becomes available,  the  timer  is restarted after each further
     * byte is received.  read returns either when the lesser of the number of bytes requested
     * or MIN byte have been read, or when the inter-byte timeout expires.  Because the timer is only
     * started after the initial byte becomes available, at least one byte will be read.
     */
    options.c_cc[VMIN] = 0;     /* Minimum number of characters for noncanonical read */
    options.c_cc[VTIME] = timeout;  /* timeout * 1/10S */
    /* print settings */
    debug(DEBUG, SERIAL_DBG_TAG, "termios.c_iflag = 0x%08x\n", options.c_iflag);
    debug(DEBUG, SERIAL_DBG_TAG, "termios.c_oflag = 0x%08x\n", options.c_oflag);
    debug(DEBUG, SERIAL_DBG_TAG, "termios.c_cflag = 0x%08x\n", options.c_cflag);
    debug(DEBUG, SERIAL_DBG_TAG, "termios.c_lflag = 0x%08x\n", options.c_lflag);

    for (j = 0; j < NCCS; j++)
        debug(DEBUG, SERIAL_DBG_TAG, "termios.c_cc[%d] = 0x%08x\n", j, options.c_cc[j]);

    debug(DEBUG, "", "%s", "\n");
    ret = tcflush(serial[i].fd, TCIOFLUSH); /* flushes both data received but not read, and data written but not transmitted */

    if (ret == -1)
    {
        perror(SERIAL_DBG_TAG);
        close(serial[i].fd);
        serial[i].fd = -1;
        memset(serial[i].devpath, 0, sizeof(serial[i].devpath));
        return -ESERIALOPENERR;
    }

    tcsetattr(serial[i].fd, TCSANOW, &options); /* the change occurs immediately */

    if (ret == -1)
    {
        perror(SERIAL_DBG_TAG);
        close(serial[i].fd);
        serial[i].fd = -1;
        memset(serial[i].devpath, 0, sizeof(serial[i].devpath));
        return -ESERIALOPENERR;
    }

    debug(DEBUG, SERIAL_DBG_TAG, "%s", "open device success\n");
    return i;
}


void serial_close(int dev)
{
    if (serial[dev].fd != -1)
    {
        tcflush(serial[dev].fd, TCIOFLUSH);
        close(serial[dev].fd);
        serial[dev].fd = -1;
    }

    memset(serial[dev].devpath, 0, sizeof(serial[dev].devpath));
}

void serial_exit(void)
{
    int i;

    for (i = 0; i < MAX_SERIAL_NUMS; i++)
    {
        if (serial[i].fd != -1)
        {
            tcflush(serial[i].fd, TCIOFLUSH);
            close(serial[i].fd);
            serial[i].fd = -1;
        }

        memset(serial[i].devpath, 0, sizeof(serial[i].devpath));
    }
}

int serial_recv(int dev, unsigned char *buf, unsigned int len)
{
#if DEBUG_LEVEL > INFO
    int i;
#endif
    int ret;
    int total = 0;

    if (serial[dev].fd == -1)
    {
        debug(WARNING, SERIAL_DBG_TAG, "%s", "please open the serial device first\n");
        return -ESERIALRDERR;
    }

    while (1)
    {
        ret = read(serial[dev].fd, &buf[total], len - total);

        if (ret == -1)
        {
            perror(SERIAL_DBG_TAG);
            return total;
        }

        if (ret == 0)
        {
            debug(INFO, SERIAL_DBG_TAG, "%s", "timeout or end-of-file\n");
            return total;
        }

        /* dump the data */
        debug(DEBUG, SERIAL_DBG_TAG, "recived %d bytes: ", ret);
#if DEBUG_LEVEL > INFO

        for (i = 0; i < ret; i++)
            printf("%02x ", buf[total + i]);

        printf("\n");
#endif
        total += ret;

        if (total == len)
        {
            debug(DEBUG, SERIAL_DBG_TAG, "%s", "recive all data success\n");
            return total;
        }
    }
}

int serial_send(int dev, const unsigned char *buf, unsigned int len)
{
#if DEBUG_LEVEL > INFO
    int i;
#endif
    int ret;
    int total = 0;

    if (serial[dev].fd == -1)
    {
        debug(WARNING, SERIAL_DBG_TAG, "%s", "please open the serial device first\n");
        return -ESERIALWRERR;
    }

    /* dump the data */
    debug(DEBUG, SERIAL_DBG_TAG, "want to send %d bytes: ", len);
#if DEBUG_LEVEL > INFO

    for (i = 0; i < len; i++)
        printf("%02x ", buf[i]);

    printf("\n");
#endif

    while (1)
    {
        ret = write(serial[dev].fd, &buf[total], len - total);

        if (ret == -1)
        {
            perror(SERIAL_DBG_TAG);
            return total;
        }

        debug(DEBUG, SERIAL_DBG_TAG, "write %d bytes success\n", ret);
        total += ret;

        if (total == len)
        {
            debug(DEBUG, SERIAL_DBG_TAG, "%s", "send all data success\n");
            return len;
        }
    }
}

